package com.github.aesteve.vertx.nubes.reflections.visitors;

import com.github.aesteve.vertx.nubes.Config;
import com.github.aesteve.vertx.nubes.annotations.Controller;
import com.github.aesteve.vertx.nubes.annotations.filters.AfterFilter;
import com.github.aesteve.vertx.nubes.annotations.filters.BeforeFilter;
import com.github.aesteve.vertx.nubes.handlers.AnnotationProcessor;
import com.github.aesteve.vertx.nubes.handlers.Processor;
import com.github.aesteve.vertx.nubes.reflections.Filter;
import com.github.aesteve.vertx.nubes.reflections.RouteRegistry;
import com.github.aesteve.vertx.nubes.reflections.factories.AuthenticationFactory;
import com.github.aesteve.vertx.nubes.routing.MVCRoute;
import io.vertx.core.VertxException;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.BiConsumer;

public class ControllerVisitor<T> extends BaseVisitor<T> {

  private final Method[] methods;
  final String basePath;
  final AuthenticationFactory authFactory;
  final RouteRegistry routeRegistry;
  final Map<Class<? extends Annotation>, BiConsumer<RoutingContext, ?>> returnHandlers;
  final Set<Processor> processors;
  private final Set<Filter> beforeFilters;
  private final Set<Filter> afterFilters;


  public ControllerVisitor(Class<T> controllerClass, Config config, Router router, AuthenticationFactory authFactory, RouteRegistry routeRegistry, Map<Class<? extends Annotation>, BiConsumer<RoutingContext, ?>> returnHandlers) {
    super(controllerClass, config, router);
    this.routeRegistry = routeRegistry;
    this.returnHandlers = returnHandlers;
    methods = controllerClass.getDeclaredMethods();
    Controller base = clazz.getAnnotation(Controller.class);
    basePath = base.value();
    this.authFactory = authFactory;
    processors = new LinkedHashSet<>();
    beforeFilters = new TreeSet<>();
    afterFilters = new TreeSet<>();
  }

  public List<MVCRoute> visit() throws IllegalAccessException, InstantiationException {
    instance = clazz.newInstance();
    List<MVCRoute> routes = new ArrayList<>();
    try {
      injectServices();
    } catch (IllegalAccessException iae) {
      throw new VertxException(iae);
    }
    extractFilters();
    for (Method method : methods) {
      MethodVisitor<T> visitor = new MethodVisitor<>(this, method);
      routes.addAll(visitor.visit());
    }
    for (MVCRoute route : routes) {
      route.addProcessorsFirst(processors);
      route.addBeforeFilters(beforeFilters);
      route.addAfterFilters(afterFilters);
    }
    return routes;
  }

  private void extractFilters() {
    if (hasSuperClass()) {
      ControllerVisitor<?> superClass = new ControllerVisitor<>(clazz.getSuperclass(), config, router, authFactory, routeRegistry, returnHandlers);
      superClass.extractFilters();
      beforeFilters.addAll(superClass.beforeFilters);
      afterFilters.addAll(superClass.afterFilters);
      processors.addAll(superClass.processors);
    }
    for (Method method : methods) {
      BeforeFilter beforeAnnot = method.getAnnotation(BeforeFilter.class);
      AfterFilter afterAnnot = method.getAnnotation(AfterFilter.class);
      if (beforeAnnot != null) {
        beforeFilters.add(new Filter(method, beforeAnnot));
      } else if (afterAnnot != null) {
        afterFilters.add(new Filter(method, afterAnnot));
      }
    }
    for (Annotation annotation : clazz.getDeclaredAnnotations()) {
      AnnotationProcessor<?> controllerProcessor = config.getAnnotationProcessor(annotation);
      if (controllerProcessor != null) {
        processors.add(controllerProcessor);
      }
    }
  }

  private boolean hasSuperClass() {
    return clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Object.class);
  }

}
